/******************************************************************************************************
Copyright (C) 2013 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals Str, Molecule, Multiplets, Application, NMRAssignments, NMRSpectrum, settings, print, Atom, MessageBox, MnUi, FileDialog, Dir, File, TextStream, AssignmentReporter*/
/*jslint plusplus: true, indent: 4*/

function assignmentReport() {
	'use strict';
	var mol, assign, spectra, foundMulti, specIndex, spectrum, multi, standardReporter, correlationsReporter,
		diag, drawnItems, tableText, pageItem, i, standardTable, correlationsTable, table, cols, j, width,
		addNumberOfNuclides, addMultiplicity, parameters, correlationsTableStart, assignmentsArray, format,
		multiCarbon = new Multiplets(),
		lines = 25,
		dw = Application.mainWindow.activeDocument,
		clipBoardKey = "Correlation Reporter/ClipBoard",
		correlations2DKey  = "Correlation Reporter/2D Correlations",
		orderKey = "Correlation Reporter/Order by shift",
		decimalsForProtonKey = "Correlation Reporter/Number of decimals for proton",
		decimalsForCarbonKey = "Correlation Reporter/Number of decimals for carbon and x-nuclei",
		showShiftKey = "Correlation Reporter/Show Shift",
		includeCarbonMultiplicityKey = "Correlation Reporter/Show Carbon Multiplicity and J Couplings",
		includeMultiplicityKey = "Correlation Reporter/Include Multiplicity",
		reportMeanChemicalShiftsKey = "Correlation Reporter/Report Mean Chemical Shifts",
		addNumberOfNuclidesKey = "Correlation Reporter/Add number of nuclides",
		includeAtomTypeKey = "Correlation Reporter/Include Atom Type",
		exportToFileKey = "Correlation Reporter/Export to File",
		exportingFormatKey = "Correlation Reporter/Exporting Format",
		dropLinesWithoutCorrelationKey = "Correlation Reporter/Drop Lines Without Correlation",
		formatKey = "Correlation Reporter/Format",
		showDeltaForCarbonKey = "Correlation Reporter/Show Delta for carbon",
		reportTxtFileKey = "Correlation Reporter/Report Txt File",
		reportHTMLFileKey = "Correlation Reporter/Report HTML File";

	function getActiveMolecule(aDocWin, aMolPlugin) {

		var molec = aMolPlugin.activeMolecule();
		if (molec.isValid()) {
			return molec;
		}

		if (aDocWin.itemCount("Molecule") === 1) {
			molec = new Molecule(aDocWin.item(0, "Molecule"));
			return molec;
		}
		return undefined;
	}

	function createHTMLReport(table, index, lines) {

		var i, j, output;

		function fillVoids(cell) {
			if (cell === undefined || cell === "") {
				return "-";
			}
			return cell;
		}


		output = "<font style=\"font-size: 8pt; font-family: Arial; color: black\">";
		output += "<html><head>";
		output += "<title>Correlations Table</title>";
		output += "</head><body>";
		output += '\n<table border="1" cellSpacing="0" cellPadding="4" width="100%">';
		output += '\n<tr style="background-color:silver">';


		for (i = 0; i < table[0].length; i++) {
			output += '<td><b>' + table[0][i] + '</b></td>';
		}
		output += '</tr>';

		if (index === undefined) {
			for (i =  1; i < table.length; i++) {
				output += '<tr>';
				for (j = 0; j < table[i].length; j++) {
					output += '<td>' + fillVoids(table[i][j]) + '</td>';
				}
				output += '</tr>';
			}
		} else {

			if (index !== 0) {
				index = index * lines;
			}

			for (i = index + 1; (i < table.length && (i <= (index + lines))); i++) {
				output += '<tr>';
				for (j = 0; j < table[i].length; j++) {
					output += '<td>' + fillVoids(table[i][j]) + '</td>';
				}
				output += '</tr>';
			}
		}
		output += '</table>';
		output += '</body></html>';

		return output;
	}



	function exportToFile(aFormat) {

		function formatHeader(aHeader) {
			var re, header = aHeader.toString();
			re = new RegExp("<(.*?)>|[&;]", 'g');
			header = header.replace(re, '');
			return header;
		}

		var i, file, stream, dataFile;

		if (aFormat) {
			dataFile = FileDialog.getSaveFileName("*.txt", "Save report in text format", settings.value(reportTxtFileKey, Dir.home()));
		} else {
			dataFile = FileDialog.getSaveFileName("*.html", "Save report in HTML format", settings.value(reportHTMLFileKey, Dir.home()));
		}

		if (dataFile !== "") {

			file = new File(dataFile);

			if (aFormat) {
				settings.setValue(reportTxtFileKey, dataFile);
			} else {
				settings.setValue(reportHTMLFileKey, dataFile);
			}

			file.open(File.WriteOnly);
			stream = new TextStream(file);

			if (aFormat) {
				for (i = 0; i < table.length; i++) {
					if (i === 0) {
						stream.writeln(formatHeader(table[i].join("\t")));
					} else {
						stream.writeln(table[i].join("\t"));
					}
					stream.flush();
				}
			} else {
				tableText = createHTMLReport(table);
				stream.write(tableText);
				stream.flush();
			}
			file.close();
		}
	}



	function getCorrelationsArray() {

		return ["HSQC", "HMBC", "H2BC", "COSY", "NOESY", "TOCSY", "ROESY"];
	}

	function getCorrelationsDescriptions() {
		return [assign.realAssignedExp("HSQC"), "HMBC", "H2BC", assign.realAssignedExp("COSY"), assign.realAssignedExp("NOESY"), assign.realAssignedExp("TOCSY"), "ROESY"];
	}

	function getAssignmentsArray() {
		var headerArray = [];
		headerArray.push("No");

		if (!addNumberOfNuclides && !addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub>");
		} else if (addNumberOfNuclides && !addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub> (nH)");
		} else if (!addNumberOfNuclides && addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub> (Mul, <i>J</i>)");
		} else if (addNumberOfNuclides && addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub> (Mul, <i>J</i>, nH)");
		}
		return headerArray;
	}

	function getAssignmentsDescriptions() {
		var headerArray = [];
		headerArray.push("No");

		if (!addNumberOfNuclides && !addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub>");
		} else if (addNumberOfNuclides && !addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub> (nH)");
		} else if (!addNumberOfNuclides && addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub> (Multiplicity, <i>J</i>)");
		} else if (addNumberOfNuclides && addMultiplicity) {
			headerArray.push("&delta; <sub>H</sub> (Multiplicity, <i>J</i>, nH)");
		}
		return headerArray;
	}


	function getCarbonMultiplets() {
		var index, nucleus, multiC = new Multiplets();

		for (index = 0; index < spectra; index++) {

			spectrum = new NMRSpectrum(dw.item(index, "NMR Spectrum"));
			nucleus = spectrum.nucleus();

			if (nucleus === "13C") {
				multiC = spectrum.multiplets();
			}

		}

		return multiC;

	}


	if (dw === undefined || Application.molecule === undefined) {
		return;
	}

	mol = getActiveMolecule(dw, Application.molecule);
	if (mol === undefined || !mol.isValid()) {
		MessageBox.critical("Invalid Molecule");
		return;
	}

	assign = new NMRAssignments(mol);
	spectra = dw.itemCount("NMR Spectrum");
	foundMulti = false;
	specIndex = 0;

	while (!foundMulti  && specIndex < spectra) {
		spectrum = new NMRSpectrum(dw.item(specIndex, "NMR Spectrum"));

		if ((spectrum.nucleus() === "1H") && (spectrum.dimCount === 1)) {
			foundMulti = true;
			multi = spectrum.multiplets();
		} else {
			specIndex++;
		}
	}
	if (!foundMulti) {
		MessageBox.critical("Invalid Spectrum");
		return;
	}

	diag = Application.loadUiFile("ricares:assignmentReport.ui");

	diag.widgets.gb2DCorrelations.checked = settings.value(correlations2DKey, true);
	diag.widgets.sbDecimalsForProton.value = settings.value(decimalsForProtonKey, 2);
	diag.widgets.sbDecimalsForCarbon.value = settings.value(decimalsForCarbonKey, 1);
	diag.widgets.ckOrder.checked = settings.value(orderKey, true);
	diag.widgets.gbShowDeltaForCarbon.checked = settings.value(showShiftKey, true);
	diag.widgets.gbExportToFile.checked = settings.value(exportToFileKey, false);
	diag.widgets.rbText.checked = settings.value(exportingFormatKey, true);
	diag.widgets.rbHTML.checked = !diag.widgets.rbText.checked;
	diag.widgets.ckClipBoard.checked = settings.value(clipBoardKey, true);
	diag.widgets.ckIncludeMultiplicity.checked = settings.value(includeMultiplicityKey, false);
	diag.widgets.ckIncludeCarbonMultiplicity.checked = settings.value(includeCarbonMultiplicityKey, false);
	diag.widgets.ckReportMeanChemicalShifts.checked = settings.value(reportMeanChemicalShiftsKey, true);
	diag.widgets.ckAddNumberOfNuclides.checked = settings.value(addNumberOfNuclidesKey, false);
	diag.widgets.ckIncludeAtomType.checked = settings.value(includeAtomTypeKey, false);
	diag.widgets.ckDropLinesWithoutCorrelation.checked = settings.value(dropLinesWithoutCorrelationKey, false);
	diag.widgets.ckShowDeltaForCarbon.checked = settings.value(showDeltaForCarbonKey, true);
	format =  settings.value(formatKey, 1);

	if (format === 0) {
		diag.widgets.rbN.checked = true;
		diag.widgets.rbDeltaN.checked = false;
		diag.widgets.rbCnDelta.checked = false;
	} else if (format === 1) {
		diag.widgets.rbN.checked = false;
		diag.widgets.rbDeltaN.checked = true;
		diag.widgets.rbCnDelta.checked = false;
	} else if (format === 2) {
		diag.widgets.rbN.checked = false;
		diag.widgets.rbDeltaN.checked = false;
		diag.widgets.rbCnDelta.checked = true;
	}

	drawnItems = [];

	if (diag.exec()) {

		settings.setValue(correlations2DKey, diag.widgets.gb2DCorrelations.checked);
		settings.setValue(decimalsForProtonKey, diag.widgets.sbDecimalsForProton.value);
		settings.setValue(decimalsForCarbonKey, diag.widgets.sbDecimalsForCarbon.value);
		settings.setValue(orderKey, diag.widgets.ckOrder.checked);
		settings.setValue(showShiftKey, diag.widgets.gbShowDeltaForCarbon.checked);
		settings.setValue(exportToFileKey, diag.widgets.gbExportToFile.checked);
		settings.setValue(exportingFormatKey, diag.widgets.rbText.checked);
		settings.setValue(clipBoardKey, diag.widgets.ckClipBoard.checked);
		settings.setValue(includeMultiplicityKey, diag.widgets.ckIncludeMultiplicity.checked);
		settings.setValue(includeCarbonMultiplicityKey, diag.widgets.ckIncludeCarbonMultiplicity.checked);
		settings.setValue(reportMeanChemicalShiftsKey, diag.widgets.ckReportMeanChemicalShifts.checked);
		settings.setValue(includeAtomTypeKey, diag.widgets.ckIncludeAtomType.checked);
		settings.setValue(addNumberOfNuclidesKey, diag.widgets.ckAddNumberOfNuclides.checked);
		settings.setValue(dropLinesWithoutCorrelationKey, diag.widgets.ckDropLinesWithoutCorrelation.checked);
		settings.setValue(showDeltaForCarbonKey, diag.widgets.ckShowDeltaForCarbon.checked);

		if (diag.widgets.rbN.checked) {
			settings.setValue(formatKey, 0);
		} else if (diag.widgets.rbDeltaN.checked) {
			settings.setValue(formatKey, 1);
		} else if (diag.widgets.rbCnDelta.checked) {
			settings.setValue(formatKey, 2);
		}

		addNumberOfNuclides = diag.widgets.ckAddNumberOfNuclides.checked;
		addMultiplicity =  diag.widgets.ckIncludeMultiplicity.checked;

		assignmentsArray = getAssignmentsArray();
		correlationsTableStart = assignmentsArray.length;

		standardReporter = new AssignmentReporter(assignmentsArray, "Main", getAssignmentsDescriptions(), "Correlation Reporter/H&C");
		correlationsReporter = new AssignmentReporter(getCorrelationsArray(), "2D Correlations", getCorrelationsDescriptions(), "Correlation Reporter/2D");

		parameters = {};
		parameters.protonDecimals = diag.widgets.sbDecimalsForProton.value;
		parameters.carbonDecimals = diag.widgets.sbDecimalsForCarbon.value;
		parameters.assignmentObject = assign;
		parameters.molecule = mol;
		parameters.reporter = standardReporter;
		parameters.multi = multi;
		parameters.addNumberOfNuclides = addNumberOfNuclides;
		parameters.addMultiplicity = addMultiplicity;
		parameters.showDeltaForCarbon = diag.widgets.ckShowDeltaForCarbon.checked;
		parameters.addCarbonMultiplicity = diag.widgets.ckIncludeCarbonMultiplicity.checked;
		parameters.addAtomType = diag.widgets.ckIncludeAtomType.checked;
		parameters.reportMeanChemicalShifts = diag.widgets.ckReportMeanChemicalShifts.checked;

		if (parameters.addCarbonMultiplicity) {
			multiCarbon = getCarbonMultiplets();
		}
		parameters.multiCarbon = multiCarbon;

		standardTable = AssignmentReporter.assignmentReport(parameters);

		if (diag.widgets.gb2DCorrelations.checked) {
			parameters.reporter = correlationsReporter;

			if (diag.widgets.rbN.checked) {
				parameters.format = 0;
			} else if (diag.widgets.rbDeltaN.checked) {
				parameters.format = 1;
			} else if (diag.widgets.rbCnDelta.checked) {
				parameters.format = 2;
			}

			correlationsTable = AssignmentReporter.assignmentReportWithCorrelations(parameters);
		}

		if (diag.widgets.gbShowDeltaForCarbon.checked) {
			correlationsTableStart++;
		}

		table = AssignmentReporter.getFinalTable(standardTable, correlationsTable);


		if (diag.widgets.ckOrder.checked) {
			table = AssignmentReporter.getOrderedTable(table, diag.widgets.ckIncludeAtomType.checked);
		}

		if (diag.widgets.ckShowDeltaForCarbon.checked) {
			table = AssignmentReporter.removeVoidAssignmentsRows(table, standardReporter.xNuclidesIndex);

		} else {
			table = AssignmentReporter.removeVoidAssignmentsRows(table, 1);
		}

		if (diag.widgets.ckDropLinesWithoutCorrelation.checked && diag.widgets.gb2DCorrelations.checked) {
			table = AssignmentReporter.removeVoidCorrelationsRows(table, standardReporter.xNuclidesIndex);
		}

		table = AssignmentReporter.removeVoidColumns(table);


		if (diag.widgets.ckClipBoard.checked) {

			tableText = createHTMLReport(table);
			pageItem = Application.draw.text(tableText, "Report Special", "Assignments Proton", true);
			drawnItems.push(pageItem);

			for (i = 1; i < drawnItems.length; i++) {
				drawnItems[i].top = drawnItems[i - 1].top;
				drawnItems[i].left = drawnItems[i - 1].right;
			}
			settings.setValue(clipBoardKey, true);
			dw.setSelection(drawnItems);
			Application.mainWindow.doAction("action_Edit_Copy");
			Application.mainWindow.activeDocument.curPage().deleteItems(drawnItems);
			dw.update();

		} else {

			cols = Math.ceil((table.length - 1) / lines);
			for (i = 0; i < cols; i++) {

				tableText = createHTMLReport(table, i, lines);
				pageItem = Application.draw.text(tableText, "Report Special", "Assignments Proton", true);
				drawnItems.push(pageItem);
			}
			for (j = 1; j < drawnItems.length; j++) {
				drawnItems[j].top = drawnItems[j - 1].top;
				width = drawnItems[j].width;
				drawnItems[j].right = drawnItems[j - 1].right + width;
				drawnItems[j].left = drawnItems[j - 1].right;
				drawnItems[j].update();
				dw.update();
			}
			settings.setValue(clipBoardKey, false);
			Application.mainWindow.activeDocument.curPage().update();
		}


		if (diag.widgets.gbExportToFile.checked) {
			exportToFile(diag.widgets.rbText.checked);
		}
	}
}

function AssignmentReporter(aArray, aDescription, aCorrelationDesc) {
	'use strict';
	this.fCorrelations = aArray;
	this.fDescription = aDescription;
	this.fCorrelationsDescription = aCorrelationDesc;
	this.xNuclides = {"C": 2};
	this.xNuclidesIndex = 2;
	this.nucleids = {};
}

AssignmentReporter.prototype = {};


AssignmentReporter.getXNucleus = function (aElement, aAssignmentReporter) {
	'use strict';
	if (aAssignmentReporter.xNuclides[aElement] === undefined && aElement !== "H") {
		aAssignmentReporter.xNuclidesIndex++;
		aAssignmentReporter.xNuclides[aElement] = aAssignmentReporter.xNuclidesIndex;
	}
	return aAssignmentReporter.xNuclides[aElement];

};


AssignmentReporter.preProcessShiftArray = function (shiftArray, decimals, reportMeanValue) {
	'use strict';
	var i, procShiftArray = [];

	for (i = 0; i < shiftArray.length; i++) {
		if (shiftArray[i].max === shiftArray[i].min) {
			procShiftArray[i] = shiftArray[i].max.toFixed(decimals);
		} else {
			if (reportMeanValue) {
				procShiftArray[i] = shiftArray[i].shift.toFixed(decimals);
			} else {
				procShiftArray[i] = [shiftArray[i].min.toFixed(decimals), shiftArray[i].max.toFixed(decimals)];
			}
		}
	}
	return procShiftArray;
};


AssignmentReporter.assignmentReport = function (parameters) {
	'use strict';

	var i, j, k, at, noEqHs, hIndex, atomLabel, atomRow, h, shift, hIsHeavyIndex, notation, skip, element, shifts, atomNH,
		carbonReport, shiftsC, atomRowWithType, index, heavyAtomAt, heavyAtomStr, tableRowsWithType, headerRowWithType,
		aAssignmentObject = parameters.assignmentObject,
		aMolecule = parameters.molecule,
		aAssignmentReporter = parameters.reporter,
		aMultiplets = parameters.multi,
		aCarbonMultiplets = parameters.multiCarbon,
		reportMeanChemicalShifts = parameters.reportMeanChemicalShifts,
		aAddNumberOfNuclides = parameters.addNumberOfNuclides,
		aAddMultiplicity = parameters.addMultiplicity,
		aAddCarbonMultiplicity = parameters.addCarbonMultiplicity,
		aDecimals = parameters.protonDecimals,
		aDecimalsForCarbon = parameters.carbonDecimals,
		aCarbonAssignments = parameters.showDeltaForCarbon,
		aAddAtomType = parameters.addAtomType,
		aCount = aMolecule.atomCount,
		tableRows = {},
		headerRow = [];

	if (aAssignmentReporter !== undefined) {
		for (i = 0; i < aAssignmentReporter.fCorrelations.length; i++) {
			headerRow.push(aAssignmentReporter.fCorrelationsDescription[i]);
		}
	}

	for (at = 1; at <= aCount; at++) {
		noEqHs = aAssignmentObject.notEqHs(at);
		skip = true;
		hIsHeavyIndex = false;
		atomLabel = aMolecule.atom(at).number;
		element = aMolecule.atom(at).elementSymbol;
		atomNH = aMolecule.atom(at).nHAll;
		notation = aMolecule.atom(at).protonNotation;

		if (noEqHs.length === 0 && element !== "H" && aCarbonAssignments) {

			atomRow = [];
			atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at);
			shifts = [];
			atomRow[1] = "";

			shift =  aAssignmentObject.chemShiftArr(at);
			if (shift) {
				shiftsC = AssignmentReporter.preProcessShiftArray(shift, aDecimalsForCarbon, reportMeanChemicalShifts);
				carbonReport = AssignmentReporter.findInformation(aDecimalsForCarbon, aCarbonMultiplets, shiftsC, 0, false, aAddCarbonMultiplicity);
				if (aAddCarbonMultiplicity && !carbonReport.endsWith(")")) {
					carbonReport += "(s)";
				}
				atomRow[AssignmentReporter.getXNucleus(element, aAssignmentReporter)] = carbonReport;
			} else {
				if (element !== "C") {
					atomRow[AssignmentReporter.getXNucleus(element, aAssignmentReporter)] = "";
				} else {
					atomRow[AssignmentReporter.getXNucleus(element, aAssignmentReporter)] = "-";
				}
			}
			tableRows[at + "_" + atomRow[0]] = atomRow;

		} else {
			for (hIndex = 0; hIndex < noEqHs.length; hIndex++) {
				atomRow = [];
				atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at);
				atomRow[1] = "";
				shifts = [];
				h = noEqHs[hIndex];
				if (h === 0) {
					hIsHeavyIndex = true;//H not attached to any C
				}

				if (noEqHs.length > 1) {
					atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, true);
				} else if (noEqHs.length > 0) {
					atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, false);
				}

				shift =  aAssignmentObject.chemShiftArr(at, h);
				if (shift) {
					skip = false;
					shifts = AssignmentReporter.preProcessShiftArray(shift, aDecimals, reportMeanChemicalShifts);

					if (aAssignmentReporter.nucleids[atomRow[0] + "_" + shift]) {
						aAssignmentReporter.nucleids[atomRow[0] + "_" + shift]++;
					} else {
						if (noEqHs.length > 1) {
							aAssignmentReporter.nucleids[atomRow[0] + "_" + shift] = 1;
						} else {
							if (shift.length > 1 || hIsHeavyIndex) {
								aAssignmentReporter.nucleids[atomRow[0] + "_" + shift] = 1;
							} else {
								aAssignmentReporter.nucleids[atomRow[0] + "_" + shift] = atomNH;
							}
						}
					}
					atomNH = aAssignmentReporter.nucleids[atomRow[0] + "_" + shift];
					atomRow[1] = AssignmentReporter.findInformation(aDecimals, aMultiplets, shifts, atomNH, aAddNumberOfNuclides, aAddMultiplicity);

				} else {
					atomRow[1] = "-";
				}

				if (aCarbonAssignments) {

					shift =  aAssignmentObject.chemShiftArr(at);

					if (!hIsHeavyIndex && shift && element !== "H") {
						skip = false;

						if (shift) {
							shiftsC = AssignmentReporter.preProcessShiftArray(shift, aDecimalsForCarbon, reportMeanChemicalShifts);
							carbonReport = AssignmentReporter.findInformation(aDecimalsForCarbon, aCarbonMultiplets, shiftsC, 0, false, aAddCarbonMultiplicity);
							if (aAddCarbonMultiplicity && !carbonReport.endsWith(")")) {
								carbonReport += "(s)";
							}
							atomRow[AssignmentReporter.getXNucleus(element, aAssignmentReporter)] = carbonReport;
						} else {
							atomRow[AssignmentReporter.getXNucleus(element, aAssignmentReporter)] = "-";
						}

					}

				}
				tableRows[at + "_" + atomRow[0]] = atomRow;

			}

		}

	}

	if (aAddAtomType) {
		tableRowsWithType = {};
		for (index in tableRows) {
			if (tableRows.hasOwnProperty(index)) {
				at = index.split("_")[0];
				atomRow = tableRows[index];
				atomRowWithType = [];
				for (k = atomRow.length - 1; k > 0; k--) {
					atomRowWithType[1 + k] = atomRow[k];
				}
				atomRowWithType[0] = atomRow[0];
				heavyAtomAt = new Atom(aMolecule.atom(AssignmentReporter.stringToAtomIndex(at)));
				heavyAtomStr = heavyAtomAt.elementSymbol;
				if (heavyAtomAt.nHAll === 1) {
					heavyAtomStr += "H";
				} else if (heavyAtomAt.nHAll > 1) {
					heavyAtomStr += "H<sub>" + heavyAtomAt.nHAll + "<sub>";
				}
				atomRowWithType[1] = heavyAtomStr;
				tableRowsWithType[index] = atomRowWithType;
			}
		}
	}

	for (j in aAssignmentReporter.xNuclides) {
		if (aAssignmentReporter.xNuclides.hasOwnProperty(j) && !aAddCarbonMultiplicity) {
			headerRow.push("&delta; <sub>" + j + "</sub>");
		}
		if (aAssignmentReporter.xNuclides.hasOwnProperty(j) && aAddCarbonMultiplicity) {
			headerRow.push("&delta; <sub>" + j + "</sub>" + " (Multiplicity, <i>J</i>)");
		}
	}
	tableRows.header = headerRow;

	if (aAddAtomType) {
		headerRowWithType = [tableRows.header[0], 'Atom Type'];
		for (i = 1; i < tableRows.header.length; i++) {
			headerRowWithType.push(tableRows.header[i]);
		}
		tableRowsWithType.header = headerRowWithType;
	}

	if (aAddAtomType) {
		return tableRowsWithType;
	}
	if (!aAddAtomType) {
		return tableRows;
	}

};


AssignmentReporter.assignmentReportWithCorrelations = function (parameters) {
	'use strict';

	var i, at, noEqHs, hIndex, atomRowHeavy, atomRowProton, h, c, shift, atomLabel, element, correlations, notation,
		aAssignmentObject = parameters.assignmentObject,
		aMolecule = parameters.molecule,
		aAssignmentReporter = parameters.reporter,
		aFormat = parameters.format,
		aProtonDecimals = parameters.protonDecimals,
		aCarbonDecimals = parameters.carbonDecimals,
		correlationsArray = [],
		aCount = aMolecule.atomCount,
		tableRows = {},
		headerRow = [];


	function sortFunctionForFloats(a, b) {
		return parseFloat(a) - parseFloat(b);
	}

	if (aAssignmentReporter !== undefined) {
		for (i = 0; i < aAssignmentReporter.fCorrelations.length; i++) {
			headerRow.push(aAssignmentReporter.fCorrelationsDescription[i]);
		}
	}
	tableRows.header = headerRow;

	for (at = 1; at <= aCount; at++) {
		noEqHs = aAssignmentObject.notEqHs(at);
		atomLabel = aMolecule.atom(at).number;
		element = aMolecule.atom(at).elementSymbol;
		notation = aMolecule.atom(at).protonNotation;

		atomRowHeavy = [];
		atomRowHeavy.push("");
		h = 0;
		shift = aAssignmentObject.chemShiftArr(at);
		if (shift) {
			atomRowHeavy[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, false);
		}
		if (aAssignmentReporter !== undefined) {
			for (c = 0; c < aAssignmentReporter.fCorrelations.length; c++) {
				correlations = AssignmentReporter.correlationToString(aAssignmentObject, aMolecule, aProtonDecimals, aCarbonDecimals, at, h, aAssignmentReporter.fCorrelations[c], aFormat);
				if (correlations !== "") {
					correlationsArray = correlations.split(",");
					correlationsArray.sort(sortFunctionForFloats);
					correlations = correlationsArray.toString();
				}
				atomRowHeavy.push(correlations);
			}
		}

		if (noEqHs && noEqHs.length) {
			for (hIndex = 0; hIndex < noEqHs.length; hIndex++) {
				atomRowProton = [];
				atomRowProton.push("");
				h = noEqHs[hIndex];
				shift = aAssignmentObject.chemShiftArr(at, h);
				if (shift) {
					if (noEqHs.length > 1) {
						atomRowProton[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, true);
					} else if (noEqHs.length > 0) {
						atomRowProton[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, false);
					}
				}
				if (aAssignmentReporter !== undefined) {
					for (c = 0; c < aAssignmentReporter.fCorrelations.length; c++) {
						correlations = AssignmentReporter.correlationToString(aAssignmentObject, aMolecule, aProtonDecimals, aCarbonDecimals, at, h, aAssignmentReporter.fCorrelations[c], aFormat);
						if (correlations !== "") {
							correlationsArray = correlations.split(",");
							correlationsArray.sort(sortFunctionForFloats);
							correlations = correlationsArray.toString();
						}
						atomRowProton.push(correlations);
					}
				}
//				fullRow = AssignmentReporter.combineAtomRows(atomRowHeavy, atomRowProton);
//				tableRows[at + "_" + fullRow[0]] = fullRow;
				tableRows[at + "_" + atomRowProton[0]] = atomRowProton;
			}
		} else {
			tableRows[at + "_" + atomRowHeavy[0]] = atomRowHeavy;
		}
	}
	return tableRows;
};


AssignmentReporter.combineAtomRows = function (atomRowHeavy, atomRowProton) {
	"use strict";
	var i, correlations, correlationsArray, combinedRow = [];

	function sortFunctionForFloats(a, b) {
		return parseFloat(a) - parseFloat(b);
	}

	combinedRow.push(atomRowProton[0]);
	for (i = 1; i < atomRowHeavy.length; i++) {
		correlations = atomRowHeavy[i] || "";
		if (correlations) {
			correlations += (atomRowProton[i] ? "," + (atomRowProton[i] || "") : "");
		} else {
			correlations += (atomRowProton[i] || "");
		}

		correlationsArray = correlations.split(",");
		correlationsArray.sort(sortFunctionForFloats);
		correlations = correlationsArray.toString();
		combinedRow[i] = correlations;
	}
	return combinedRow;
};


AssignmentReporter.removeVoidAssignmentsRows = function (table, lastxNuclidesIndex) {
	"use strict";
	var i, j,
		counter = {},
		newTable = [];

	for (i = 1; i < table.length; i++) {


		for (j = 1; j <= lastxNuclidesIndex; j++) {
			if (table[i][j] !== "") {
				counter[i] = true;
			}

		}
	}

	newTable.push(table[0]);
	for (i = 0; i < table.length; i++) {
		if (counter[i]) {
			newTable.push(table[i]);
		}
	}

	return newTable;
};


AssignmentReporter.removeVoidCorrelationsRows = function (table, startOfCorrelations) {
	"use strict";
	var i, j,
		counter = {},
		newTable = [];


	for (i = 1; i < table.length; i++) {
		for (j = parseInt(startOfCorrelations + 1, 10); j < table[i].length; j++) {
			if (table[i][j] !== "") {
				counter[i] = true;
			}
		}
	}

	newTable.push(table[0]);
	for (i = 0; i < table.length; i++) {
		if (counter[i]) {
			newTable.push(table[i]);
		}
	}

	return newTable;
};

AssignmentReporter.removeVoidColumns = function (table) {
	"use strict";

	var i, j, row,
		counter = [],
		newTable = [];

	for (i = 0; i < table[0].length; i++) {
		counter.push(0);
	}

	for (i = 1; i < table.length; i++) {
		for (j = 0; j < table[0].length; j++) {
			if (table[i][j] !== "" && table[i][j] !== undefined && table[i][j] !== "-") {
				counter[j]++;
			}
		}
	}

	for (i = 0; i < table.length; i++) {
		row = [];
		for (j = 0; j < counter.length; j++) {
			if (counter[j] !== 0) {
				row.push(table[i][j]);
			}
		}
		newTable.push(row);
	}
	return newTable;
};

AssignmentReporter.getFinalTable =  function (firstTable, secondTable) {
	"use strict";

	var i, j, joinedTable = [], aux = [];

	if (secondTable) {
		joinedTable.push(firstTable.header.concat(secondTable.header));
		for (i = 0; i < secondTable.header.length; i++) {
			aux.push("");
		}
	} else {
		joinedTable.push(firstTable.header);
	}

	for (i in firstTable) {
		if (firstTable.hasOwnProperty(i) && i !== "header") {
			for (j = 0; j < firstTable.header.length; j++) {
				if (firstTable[i][j] === undefined) {
					firstTable[i][j] = "";
				}
			}

			if (secondTable) {
				if (secondTable[i]) {
					joinedTable.push(firstTable[i].concat(secondTable[i].slice(1)));
				} else {
					joinedTable.push(firstTable[i].concat(aux));
				}
			} else {
				joinedTable.push(firstTable[i]);
			}
		}
	}

	return joinedTable;
};


AssignmentReporter.getOrderedTable =  function (tableRows, addAtomType) {
	'use strict';

	var i, k, a, b, temp,
		protonShiftIndex = 1,
		len = tableRows.length - 1;

	if (addAtomType) {
		protonShiftIndex = 2;
	}

	for (i = 1; i < len; i++) {
		for (k = 1; k < len; k++) {
			a = parseFloat(tableRows[k][protonShiftIndex]);
			b = parseFloat(tableRows[k + 1][protonShiftIndex]);
			if (isNaN(a)) {
				a = 0;
			}
			if (isNaN(b)) {
				b = 0;
			}
			if (a < b) {
				temp = tableRows[k + 1];
				tableRows[k + 1] = tableRows[k];
				tableRows[k] = temp;
			}
		}
	}
	return tableRows;
};

AssignmentReporter.getPpmArray =  function (table) {
	'use strict';
	var i,
		ppmArray = [];

	for (i = 1; i < table.length; i++) {
		ppmArray[i] = table[i][1];
	}
	return ppmArray;
};

AssignmentReporter.findInformation = function (decimals, multiplets, shifts, atomNH, addNumberOfNuclides, addMultiplicity) {
	'use strict';

	function greaterThan(a, b) {
		return b - a;
	}

	var js, jArray, i, j, k, found, information = "", category, shiftAt;

	for (j = 0; j < shifts.length; j++) {

		shiftAt = shifts[j];

		if (shiftAt.length === 2) {
			i = multiplets.count;
			information += (shiftAt[0] + ".." + shiftAt[1] + ", ");
			found = true;
		} else {
			i = 0;
			information += shiftAt;
			found = false;
		}

		while (i < multiplets.count && !found) {

			if (multiplets.at(i).delta.toFixed(decimals) === shifts[j]) {
				found = true;

				if (addNumberOfNuclides || addMultiplicity) {
					information += " (";

					if (addMultiplicity) {

						category = multiplets.at(i).category.toLowerCase();
						information += category;

						if (category !== "m" && category !== "s") {
							js = multiplets.at(i).jList();
							information += ", ";
							jArray = [];
							for (k = 0; k < js.length; k++) {
								jArray[k] = js.at(k).toFixed(1);
							}

							jArray.sort(greaterThan); //sort descending

							for (k = 0; k < jArray.length; k++) {
								if (k === 0) {
									information += jArray[k];
								} else {
									information += "," + jArray[k];
								}
							}

							information += " Hz";
						}
					}

					if (addNumberOfNuclides) {
						if (addMultiplicity) {
							information += ", ";
						}

						information += atomNH + "H";

					}
					information += "), ";
				} else {
					information += ", ";
				}

			} else {
				i++;
			}
		}

		if (!found) {
			information += ", ";
		}

	}

	if (information !== "") {
		information = information.substring(0, information.length - 2);
	}

	return information;
};

AssignmentReporter.atomIndexToString = function (aNotation, aAtomLabel, aAtomNumber, aHIndex, aUseHIndex) {
	'use strict';
	var str = aAtomLabel;

	if (str === "") {
		str = aAtomNumber.toString();
	}
	if (aUseHIndex) {
		str += AssignmentReporter.toCurrentNotation(Str.hIndexToLetter(aHIndex), aNotation);
	}
	return str;
};


AssignmentReporter.stringToAtomIndex = function (aString) {
	'use strict';
	var aIndex = parseInt(aString, 10);

	return aIndex;
};


AssignmentReporter.toCurrentNotation = function (aQuote, aNotation) {
	'use strict';

	switch (aNotation) {

	case Atom.QUOTES:
		return aQuote;
	case Atom.AB:
		return aQuote === "'" ? "a" : "b";
	case Atom.ALPHABETA:
		return aQuote === "'" ? "alpha" : "beta";
	case Atom.CISTRANS:
		return aQuote === "'" ? "cis" : "trans";
	case Atom.AXEQ:
		return aQuote === "'" ? "ax" : "eq";
	default:
		return aQuote;
	}
};

AssignmentReporter.correlationToString = function (assignObject, aMolecule, protonDecimals, carbonDecimals, aAtom, aH, aCorrelation, aFormat) {
	'use strict';
	var i, cShift, cMeanShift, atomLabel,
		corrString = "",
		noEqAtoms,
		useHIndex,
		corrAtoms,
		precisionForShift,
		notation;

	corrAtoms = assignObject.correlatedAtoms(aAtom, aH, aCorrelation);

	if (corrAtoms.length) {
		for (i = 0; i < corrAtoms.length; i++) {
			notation = aMolecule.atom(corrAtoms[i].atom).protonNotation;
			cShift = assignObject.chemShift(corrAtoms[i].atom, corrAtoms[i].indexes[0]);


			if (cShift !== undefined) {
				if (i > 0) {
					corrString  += ", ";
				}

				if (aFormat === 0) {
					atomLabel = aMolecule.atom(corrAtoms[i].atom).number;
					noEqAtoms = assignObject.notEqHs(corrAtoms[i].atom);

					if (corrAtoms[i].indexes.length === 1) {
						useHIndex = (noEqAtoms.length > 1);
						corrString += AssignmentReporter.atomIndexToString(notation, atomLabel, corrAtoms[i].atom, corrAtoms[i].indexes[0], useHIndex);
					} else {
						corrString += atomLabel;
					}
				} else if (aFormat === 1) {

					atomLabel = aMolecule.atom(corrAtoms[i].atom).number;
					noEqAtoms = assignObject.notEqHs(corrAtoms[i].atom);
					cMeanShift = cShift.shift;

					if (corrAtoms[i].indexes[0] >= 1) {
						corrString += cMeanShift.toFixed(protonDecimals);

						if (corrAtoms[i].indexes[0] === 1 && noEqAtoms.length > 1) {
							atomLabel += AssignmentReporter.toCurrentNotation("'", notation);
						} else if (corrAtoms[i].indexes[0] === 2 && noEqAtoms.length > 1) {
							atomLabel += AssignmentReporter.toCurrentNotation("''", notation);
						}

						corrString += "(" + atomLabel + ")";
					} else if (corrAtoms[i].indexes.length === 1) {
						if (aMolecule.atom(corrAtoms[i].atom).elementSymbol === "C") {
							precisionForShift = carbonDecimals;
						} else {
							precisionForShift = protonDecimals;
						}
						corrString += cMeanShift.toFixed(precisionForShift);
						useHIndex = (noEqAtoms.length > 1);
						corrString += "(" + AssignmentReporter.atomIndexToString(notation, atomLabel, corrAtoms[i].atom, corrAtoms[i].indexes[0], useHIndex) + ")";
					} else {
						corrString += cMeanShift.toFixed(protonDecimals);
						corrString += "(" + atomLabel + ")";
					}
				} else {

					atomLabel = aMolecule.atom(corrAtoms[i].atom).number;
					noEqAtoms = assignObject.notEqHs(corrAtoms[i].atom);



					if (corrAtoms[i].indexes[0] >= 1) {
						corrString += "H";

						corrString += atomLabel;

						if (corrAtoms[i].indexes[0] === 1 && noEqAtoms.length > 1) {
							corrString += AssignmentReporter.toCurrentNotation("'", notation);
						} else if (corrAtoms[i].indexes[0] === 2 && noEqAtoms.length > 1) {
							corrString += AssignmentReporter.toCurrentNotation("''", notation);
						}

						precisionForShift = protonDecimals;
					} else if (corrAtoms[i].indexes.length === 1) {

						corrString += aMolecule.atom(corrAtoms[i].atom).elementSymbol;
						useHIndex = (noEqAtoms.length > 1);
						corrString += AssignmentReporter.atomIndexToString(notation, atomLabel, corrAtoms[i].atom, corrAtoms[i].indexes[0], useHIndex);
						if (aMolecule.atom(corrAtoms[i].atom).elementSymbol === "C") {
							precisionForShift = carbonDecimals;
						} else {
							precisionForShift = protonDecimals;
						}
					} else {
						corrString += "H";
						corrString += atomLabel;
						precisionForShift = protonDecimals;
					}
					cMeanShift = cShift.shift;
					corrString += "(" + cMeanShift.toFixed(precisionForShift) + ")";

				}
			}
		}
	}
	return corrString;
};

AssignmentReporter.findMultiplicity = function (decimals, multi, shift) {
	'use strict';

	function greaterThan(a, b) {
		return b - a;
	}

	var js, jArray, j,
		i = 0,
		found = false,
		multiplicity = "";

	while (i < multi.count && !found) {
		if (multi.at(i).delta.toFixed(decimals) === shift) {
			found = true;
			multiplicity = multi.at(i).category.toLowerCase();
			if (multiplicity !== "m" && multiplicity !== "s") {
				js = multi.at(i).jList();
				multiplicity += ",<i>J</i>=";
				jArray = [];
				for (j = 0; j < js.length; j++) {
					jArray[j] = js.at(j).toFixed(decimals);
				}

				jArray.sort(greaterThan); //sort descending

				for (j = 0; j < jArray.length; j++) {
					if (j === 0) {
						multiplicity += jArray[j];
					} else {
						multiplicity += "," + jArray[j];
					}
				}

				multiplicity += " Hz";
			}
		} else {
			i++;
		}
	}
	return multiplicity;
};


AssignmentReporter.assignmentReportCorrelations = function (decimals, aAssignmentObject, aMolecule, aAssignmentReporter, aMulti) {
	'use strict';

	//Deprecated

	var i, at, noEqHs, hIndex, atomLabel, atomRow, h, shift, hIsHeavyIndex, skip, shiftH, shiftH0, shiftH1, element, notation,
		aCount = aMolecule.atomCount,
		tableRows = {},
		headerRow = [];

	if (aAssignmentReporter !== undefined) {
		for (i = 0; i < aAssignmentReporter.fCorrelations.length; i++) {
			headerRow.push(aAssignmentReporter.fCorrelationsDescription[i]);
		}
	}
	tableRows.header = headerRow;

	for (at = 1; at <= aCount; at++) {
		noEqHs = aAssignmentObject.notEqHs(at);
		skip = true;
		hIsHeavyIndex = false;
		atomLabel = aMolecule.atom(at).number;
		element = aMolecule.atom(at).elementSymbol;
		notation =  aMolecule.atom(at).protonNotation;

		if (noEqHs.length === 0  && element === "C") {
			atomRow = [];
			atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at);
			atomRow[1] = "";
			atomRow[2] = AssignmentReporter.findMultiplicity(decimals, aMulti, atomRow[1]);
			shift =  aAssignmentObject.chemShiftArr(at);
			if (shift) {
				if (shift[1]) {
					shiftH0 = shift[0].shift.toFixed(decimals);
					shiftH1 = shift[1].shift.toFixed(decimals);
					atomRow[3] = shiftH0 + "," + shiftH1;
				} else {
					atomRow[3] = shift[0].shift.toFixed(decimals);
				}
			} else {
				atomRow[3] = "";
			}
			tableRows[at + "_" + atomRow[0]] = atomRow;
		} else {

			for (hIndex = 0; hIndex < noEqHs.length; hIndex++) {
				atomRow = [];
				atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at);
				atomRow[1] = "";
				atomRow[2] = "";
				h = noEqHs[hIndex];
				if (h === 0) {
					hIsHeavyIndex = true;//H not attached to any C
				}
				shift =  aAssignmentObject.chemShiftArr(at, h);

				if (shift) {
					if (noEqHs.length > 1) {
						atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, true);
					} else if (noEqHs.length > 0) {
						atomRow[0] = AssignmentReporter.atomIndexToString(notation, atomLabel, at, h, false);
					}
					skip = false;

					if (shift[1]) {
						shiftH0 = shift[0].shift.toFixed(decimals);
						shiftH1 = shift[1].shift.toFixed(decimals);
						atomRow[1] = shiftH0 + "," + shiftH1;

					} else {
						shiftH = shift[0].shift.toFixed(decimals);
						if (atomRow[1] !== "") {
							shiftH = "," + shiftH;
							atomRow[1] += shiftH;
						} else {
							atomRow[1] = shiftH;
						}
					}
				}
				atomRow[2] = AssignmentReporter.findMultiplicity(decimals, aMulti, atomRow[1]);
				shift =  aAssignmentObject.chemShiftArr(at);
				if (!hIsHeavyIndex && shift && element === "C") {
					skip = false;

					if (shift[1]) {
						shiftH0 = shift[0].shift.toFixed(decimals);
						shiftH1 = shift[1].shift.toFixed(decimals);
						atomRow[3] = shiftH0 + "," + shiftH1;
					} else {
						atomRow[3] = shift[0].shift.toFixed(decimals);
					}
				} else {
					atomRow[3] = "";
				}
				tableRows[at + "_" + atomRow[0]] = atomRow;
			}
		}
	}
	return tableRows;
};




if (this.MnUi && MnUi.scripts_nmr) {
	MnUi.scripts_nmr.scripts_nmr_ReportAssignments = assignmentReport;
}
